// authors: Alexey Krivykh, Dmitriy Zabranskiy 2012(c)
//

import java.util.LinkedList;
import java.util.List;
import java.util.Random;

public class Incubator {
    // Responsible for creating a generation
    private static final double CROSSOVER_PROB = 0.8;
    private static final double MUTATE_PROB = 0.6;

    private static Incubator incubator;
    private static int populationSize;  // The number of exemplars in one generation
    private static Random random = new Random();
    private List<Exemplar> population = new LinkedList<Exemplar>();  // New population received from the generation
    private Incubator() {
        for (int i = 0; i < populationSize; i++) {
            population.add(new Exemplar());
        }
    }


    public static Incubator getInstance(int populationSize, int lecture_count) {
        if (incubator == null) {
            Exemplar.setLectureCount(lecture_count);
            Incubator.populationSize = populationSize;
            incubator = new Incubator();
        }
        return incubator;
    }
    // Generation of new populations
    // Search for the best exemplar in all populations
    public Exemplar generate(int generationNumber) throws CloneNotSupportedException {
        Exemplar bestExemplar = new Exemplar();

        for (int i = 0; i < generationNumber; i++) {
            System.out.print(i + ": ");
            Exemplar exemplar = gainBestExemplar();
            if (exemplar != null) {
                if (exemplar.getRating() > bestExemplar.getRating()) {
                    bestExemplar = (Exemplar) exemplar.clone();
                   // System.out.print("!!!" + bestExemplar.toString());
                }
            }
        }

        return bestExemplar;
    }
    // Create a new population
    // Search for the best exemplar of a new population
    private Exemplar gainBestExemplar() throws CloneNotSupportedException {
        double ratingSum = calcRatings();
        double[] prob = new double[populationSize];
        int[] succeedExems = new int[populationSize];
        double bestRating = 0;

        // calculating probability of exemplars
        for (int i = 0; i < populationSize; i++) {
            prob[i] = population.get(i).getRating() / ratingSum;
        }

        // calc exemplars which are going to crossover
        for (int i = 0; i < populationSize; i++) {
            double cast = random.nextDouble();
            int j = 0;

            while (cast > 0) {
                cast -= prob[j++];
            }
            succeedExems[i] = --j;
        }

        List<Exemplar> newPopulation = new LinkedList<Exemplar>();
        for (int i = 0; i < succeedExems.length; i += 2) {

            // choose pairs of exemplars for crossover
            if (random.nextDouble() <= CROSSOVER_PROB) {
                newPopulation.addAll(crossover(succeedExems[i],
                        succeedExems[i + 1]));
            } else {
                newPopulation.add((Exemplar) population.get(succeedExems[i]).clone());
                newPopulation.add((Exemplar) population.get(succeedExems[i + 1]).clone());
            }
        }

        for (int i = 0; i < succeedExems.length; i++) {
            if (random.nextDouble() <= MUTATE_PROB) {
                newPopulation.get(i).mutate();
            }
        }
        population = newPopulation;
        Exemplar ex = null;
        for (Exemplar exemplar : population) {
            if (exemplar.getRating() > bestRating) {
                ex = exemplar;
                ex.calcRating();
                bestRating = exemplar.getRating();
            }
        }
        System.out.format("%.3f%n", bestRating);
        return ex;
    }

    // process crossover with firstExem and secondExem in population list
    private List<Exemplar> crossover(int firstExem, int secondExem) {
        int[] firstSchedule = population.get(firstExem).getSchedule().clone();
        int[] secondSchedule = population.get(secondExem).getSchedule().clone();
        int crossoverPoint = random.nextInt(firstSchedule.length - 2) + 1;

        for (int i = crossoverPoint; i < firstSchedule.length; i++) {
            int tmp = firstSchedule[i];
            firstSchedule[i] = secondSchedule[i];
            secondSchedule[i] = tmp;
        }

        List<Exemplar> newExems = new LinkedList<Exemplar>();
        newExems.add(new Exemplar(firstSchedule));
        newExems.add(new Exemplar(secondSchedule));

        return newExems;
    }

    private double calcRatings() {
        double ratingSum = 0;
        for (Exemplar exemplar : population) {
            ratingSum += exemplar.getRating();
        }
        return ratingSum;
    }

}
